﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using ThePresenter.Net.NetCom;
using ThePresenter.Net.PresenterCom.Uids;
using ThePresenter.Polls;
using ThePresenter.Polls.Data;
using ThePresenter.Properties;

namespace ThePresenter
{
	public class Presentation : ISerializable, INotifyPropertyChanged
	{
		#region Fields

		/// <summary>
		/// Presentations counter.
		/// </summary>
		static int _Counter;

		/// <summary>
		/// The author of the presentation.
		/// </summary>
		string _Author;

		/// <summary>
		/// The title of the presentation.
		/// </summary>
		string _Title;

		/// <summary>
		/// The selected slide.
		/// </summary>
		Slide _SelectedSlide;

		/// <summary>
		/// The slides of the presentation.
		/// </summary>
		SlidesCollection _Slides;

		/// <summary>
		/// Binding mode of presentation.
		/// </summary>
		PresentationBindingMode _BindingMode;

		/// <summary>
		/// List of clients for UI
		/// </summary>
		BindingList<KeyValuePair<Uid, ClientInfo>> _Clients;

		/// <summary>
		/// Status of connection to server
		/// </summary>
		Boolean _PresentationOnlineStatus;

		/// <summary>
		/// List of polls
		/// </summary>
		BindingList<Poll> _Polls;

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the author.
		/// </summary>
		/// <value>The author.</value>
		public string Author
		{
			get
			{
				return _Author;
			}
			set
			{
				if (_Author != value)
				{
					_Author = value;
					OnPropertyChanged("Author");
				}
			}
		}

		/// <summary>
		/// Gets or sets the title.
		/// </summary>
		/// <value>The title.</value>
		public string Title
		{
			get
			{
				return _Title;
			}
			set
			{
				if (_Title != value)
				{
					_Title = value;
					OnPropertyChanged("Title");
				}
			}
		}

		/// <summary>
		/// Gets or sets the selected slide.
		/// </summary>
		/// <value>The selected slide.</value>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Slide SelectedSlide
		{
			get
			{
				return _SelectedSlide;
			}
			set
			{
				if (_SelectedSlide != value)
				{
					Slide old = _SelectedSlide;
					_SelectedSlide = value;
					OnPropertyChanged("SelectedSlide");
					OnSelectedSlideChanged(old, value);
				}
			}
		}

		/// <summary>
		/// Gets or sets the slides.
		/// </summary>
		/// <value>The slides.</value>
		public SlidesCollection Slides
		{
			get
			{
				return _Slides;
			}
			set
			{
				if (_Slides != value)
				{
					_Slides = value;
					OnPropertyChanged("Slides");
				}
			}
		}

		/// <summary>
		/// Gets or sets the binding mode.
		/// </summary>
		/// <value>The binding mode.</value>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public PresentationBindingMode BindingMode
		{
			get
			{
				return _BindingMode;
			}
			set
			{
				if (_BindingMode != value)
				{
					_BindingMode = value;
					Debug.WriteLine(string.Format("Setting BindingMode to {0}.", value));
					OnPropertyChanged("BindingMode");
				}
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether selected slide changes with server's.
		/// </summary>
		/// <value><c>true</c> if following server; otherwise, <c>false</c>.</value>
		public bool SlideLocked
		{
			get;
			set;
		}

		/// <summary>
		/// List of connected clients.
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public BindingList<KeyValuePair<Uid, ClientInfo>> Clients
		{
			get
			{
				return _Clients;
			}
		}

		/// <summary>
		/// Status of connection to server
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Boolean PresentationOnlineStatus
		{
			get
			{
				return _PresentationOnlineStatus;
			}
		}

		/// <summary>
		/// Gets the client count.
		/// </summary>
		/// <remarks>Used for databinding from WPF</remarks>
		/// <value>The client count.</value>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public BindingList<Poll> Polls
		{
			get
			{
				return this._Polls;
			}
		}

		/// <summary>
		/// Gets the published polls.
		/// </summary>
		/// <value>The published polls.</value>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public IEnumerable<Poll> PublishedPolls
		{
			get
			{
				return this._Polls.Where(i => i.State == ClientPollState.Published).OrderByDescending(i=>i.PublishDate);
			}
		}

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="Presentation"/> class.
		/// </summary>
		public Presentation()
		{
			_Author = Environment.UserName;
			_Title = string.Format(Resources.NewPresentationTitle, ++_Counter);
			_Slides = new SlidesCollection();
			_BindingMode = PresentationBindingMode.NotBind;
			_Clients = new BindingList<KeyValuePair<Uid, ClientInfo>>();
			_PresentationOnlineStatus = false;
			_Polls = new BindingList<Poll>();

			SlideLocked = false;
		}

		#endregion

		#region Methods

		/// <summary>
		/// Raises the <see cref="E:PropertyChanged"/> event.
		/// </summary>
		/// <param name="propertyName">The name of the property.</param>
		protected void OnPropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}

		/// <summary>
		/// Handles the ListChanged event of the _Clients control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.ComponentModel.ListChangedEventArgs"/> instance containing the event data.</param>
		void _Clients_ListChanged(object sender, ListChangedEventArgs e)
		{
			OnPropertyChanged("ClientCount");
			OnPropertyChanged("Clients");
		}

		/// <summary>
		/// Raises the <see cref="E:SelectedSlideChanged"/> event.
		/// </summary>
		/// <param name="oldValue">The old value.</param>
		/// <param name="newValue">The new value.</param>
		protected void OnSelectedSlideChanged(Slide oldValue, Slide newValue)
		{
			if (SelectedSlideChanged != null)
				SelectedSlideChanged(this, new RoutedPropertyChangedEventArgs<Slide>(oldValue, newValue));
		}

		/// <summary>
		/// Returns new instance of a  <see cref="ThePresenter.Presentation"/> from XAML.
		/// </summary>
		/// <param name="xaml">The xaml.</param>
		/// <returns>New instance of a  <see cref="ThePresenter.Presentation"/>.</returns>
		public static Presentation FromXaml(string xaml)
		{
			return (Presentation)XamlReader.Parse(xaml);
		}

		/// <summary>
		/// Creates the slide.
		/// </summary>
		/// <param name="canvas">The canvas.</param>
		public void CreateSlide(InkCanvas canvas)
		{
			Slide s = new Slide() { Canvas = canvas };

			AddSlide(s);

			SelectedSlide = Slides.Last();
		}

		/// <summary>
		/// Adds the slide.
		/// </summary>
		/// <param name="slide">The slide.</param>
		public void AddSlide(Slide slide)
		{
			Slides.Add(slide);
			
			if (SlideAdded != null)
				SlideAdded(this, new RoutedPropertyChangedEventArgs<Slide>(null, slide));
		}

		/// <summary>
		/// Selects the slide.
		/// </summary>
		/// <param name="slide">The slide.</param>
		/// <returns><c>false</c> if slide is not in collection</returns>
		public bool SelectSlide(Slide slide)
		{
			if (_Slides.Contains(slide))
			{
				SelectedSlide = slide;
				return true;
			}
			else
				return false;
		}

		/// <summary>
		/// Requests the slide select. Result depends on FollowingServer property.
		/// </summary>
		/// <param name="Slide">The slide to select.</param>
		/// <returns></returns>
		public bool RequestSlideSelect(Slide S)
		{
			if (!this.SlideLocked)
			{
				return SelectSlide(S);
			}
			else
				return false;
		}

		/// <summary>
		/// Finds the slide by UID.
		/// </summary>
		/// <param name="uid">The UID of wanted slide.</param>
		/// <returns>Slide with given UID, null if slide was not found.</returns>
		public Slide FindSlideByUid(Uid uid)
		{
					//_Slides.SingleOrDefault ?
			return _Slides.FirstOrDefault(s => s.Uid.Equals(uid));
		}

		/// <summary>
		/// Returns a <see cref="System.String"/> that represents this instance in XAML format.
		/// </summary>
		/// <returns>A <see cref="System.String"/> that represents this instance in XAML format.</returns>
		public string ToXaml()
		{
			using (var ms = new MemoryStream())
			{
				XamlWriter.Save(this, ms);

				var enc = new ASCIIEncoding();

				return enc.GetString(ms.ToArray());
			}
		}

		/// <summary>
		/// Returns a <see cref="System.String"/> that represents this instance.
		/// </summary>
		/// <returns>A <see cref="System.String"/> that represents this instance.</returns>
		public override string ToString()
		{
			return string.Format("{0} by {1}, slides: {2}", Title, Author, Slides.Count);
		}

		#region Clients methods

		public void AddClient(Uid id, ClientInfo info)
		{
			if (!this._Clients.Any(I => I.Key.Equals(id)))
			{
				this._Clients.Add(new KeyValuePair<Uid, ClientInfo>(id, info));
				_Clients_ListChanged(this, null);
			}
		}

		public void UpdateClient(Uid id, ClientInfo info)
		{
			if (this._Clients.Any(I => I.Key.Equals(id)))
			{
				this._Clients.Remove(this._Clients.First(I => I.Key.Equals(id)));
			}

			this._Clients.Add(new KeyValuePair<Uid, ClientInfo>(id, info));
			_Clients_ListChanged(this, null);
		}

		public void RemoveClient(Uid id)
		{
			if (this._Clients.Any(I => I.Key.Equals(id)))
			{
				this._Clients.Remove(this._Clients.First(I => I.Key.Equals(id)));
				_Clients_ListChanged(this, null);
			}
		}

		private ClientInfo FindClient(Uid id)
		{
			return this.Clients.First(I => ((Uid)I.Key).Equals(id)).Value;
		}

		#endregion

		#region Polls methods

		public void AddPoll(Poll P)
		{
			if (this._Polls.FirstOrDefault(i=>i.GUID.Equals(P.GUID)) == null)
			{
				this._Polls.Insert(0, P); // add poll at the begining of list
				// this._Polls.Add(P);
				OnPropertyChanged("Polls");
			}
		}

		/// <summary>
		/// Finds the poll.
		/// </summary>
		/// <param name="pollUid">The poll uid.</param>
		/// <returns>Returns poll or null if not found.</returns>
		private Poll FindPoll(Uid pollUid)
		{
			return this.Polls.FirstOrDefault(I => I.GUID.Equals(pollUid));
		}

		/// <summary>
		/// Finds the poll and publishes it.
		/// </summary>
		/// <param name="pollUid">The poll uid.</param>
		/// <returns>Returns false if poll was not found.</returns>
		public bool PublishPoll(Uid pollUid)
		{
			Poll P = FindPoll(pollUid);

			if (P != null) 
			{
				if (this.PollPublished != null) PollPublished(this, P);
				P.State = ClientPollState.Published;
				OnPropertyChanged("PublishedPolls");
				return true;
			}

			return false;
		}

		/// <summary>
		/// Called by UI to begin network communication used for poll answering.
		/// Used on client
		/// </summary>
		/// <param name="P">The P.</param>
		public void AnswerPoll(Poll P)
		{
			OnPropertyChanged("Polls");

			if (this.PollAnswered != null) PollAnswered(this, P);
		}


		/// <summary>
		/// Processes the poll answer.
		/// </summary>
		/// <param name="uid">The uid of poll.</param>
		/// <param name="answerData">The answer data.</param>
		public void ProcessPollAnswer(Uid pollUid, AnswerData answerData, Uid answerSource)
		{
			Poll P = this.FindPoll(pollUid);
			answerData.AnswerSource = answerSource;
			answerData.Author = this.FindClient(answerSource).Name;
			P.HandleAnswer(answerData);
		}

		#endregion

		public void SetConnectionStatus(Boolean connected)
		{
			this._PresentationOnlineStatus = connected;
			OnPropertyChanged("PresentationOnlineStatus");
		}

		#endregion

		#region Events

		/// <summary>
		/// Occurs when selected slide changed.
		/// </summary>
		public event RoutedPropertyChangedEventHandler<Slide> SelectedSlideChanged;

		/// <summary>
		/// Occurs when new slide is added.
		/// </summary>
		[Obsolete("Use Slides.CollectionChanged instead or create better EventHandler")]
		// cannot be Routed! MUST be fired from internal EventHandler CollectionChanged for safety !!!
		// ItemAddedEventHandler<T> : EventHandler<T>
		// ItemAddedEventArgs<T>
		public event RoutedPropertyChangedEventHandler<Slide> SlideAdded;

		public delegate void PollEventHandler(object sender, Poll poll);
		
		public event PollEventHandler PollPublished;

		public event PollEventHandler PollAnswered;

		#endregion

		#region ISerializable Members

		/// <summary>
		/// Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the target object.
		/// </summary>
		/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data.</param>
		/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization.</param>
		/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission.</exception>
		public void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			throw new NotImplementedException();
		}

		#endregion

		#region INotifyPropertyChanged Members

		/// <summary>
		/// Occurs when a property value changes.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		#endregion
	}
}